Skip to content

feat(customer): customers - #5

Merged
EdgarEldy merged 16 commits into
developfrom
feature/customer
Sep 19, 2026
Merged

EdgarEldy merged 16 commits into
developfrom
feature/customer

Conversation

@EdgarEldy

Copy link
Copy Markdown
Owner

Branch

feature/customer

Task checklist

  • core/customer: Customer entity, CustomerDto, CustomerRowMapper, CustomerMapper, CustomerDao + impl, CustomerService + impl, DaoConfig/ServiceConfig
  • ws: CustomerController, CustomerConverter
  • WebMvcConfig imports core/customer's config
  • Tests at all five layers, CustomerDao with its own fixture dataset

Commit summary

  • customer core: entity, dto, RowMapper, mapper, JdbcTemplate DAO (update returns the affected row count), service (trimming, optional fields to null, email format, FK refusal when orders reference the customer), explicit wiring
  • ws: payloads with Bean Validation, converter, controller with declarative CUSTOMER permissions, page/size validation
  • tests: customer module 56, ws 436 (834 across the reactor), including the proof that a customer with an order cannot be deleted (order inserted by SQL)

Limits documented in the project notes: email optional and not unique, 400 (request validation) versus 422 (service) for email formats, concurrent double delete is idempotent.

Test plan

  • Tests exist at every layer the branch's checklist calls for
  • Each DAO test class loads its own dedicated SQL fixture file
  • mvn clean verify is green on the whole reactor

Customer is the table-shaped class documented with the jakarta.persistence
annotations and CustomerDto is the business shape the service exposes. The
telephone, email and address are optional, so they are nullable on both sides.

The two shapes stay separate classes even though their fields are identical.
The RowMapper reads one row into a Customer and keeps the three optional
columns as null when the database holds NULL. The entity mapper converts between
Customer and CustomerDto in both directions.

Neither queries the database or contains a business rule.
CustomerDaoImpl runs the SQL with JdbcTemplate through AbstractDao: a page ordered
by id with LIMIT and OFFSET, a count, a lookup, INSERT ... RETURNING id, an update
that returns the number of affected rows and a delete.

Returning the affected row count lets the service detect that a row was deleted
concurrently. The DAO works in entity types only and has no stereotype annotation.
CustomerServiceImpl trims and bounds the names, turns blank optional fields into
null, checks the format of an email when one is given, and reports an update that
affected no row as a not-found error. A customer referenced by orders cannot be
deleted: the foreign key raises a DataIntegrityViolationException which the service turns
into a business refusal, so no order code is needed yet.

The service works in dto types, pages through PageDto and is transactional through its
interface proxy.
DaoConfig and ServiceConfig declare the beans typed by interface, and ServiceConfig
imports DaoConfig so importing it alone is enough.

The service bean is proxied for @transactional through its interface.
CustomerRequest requires the two names and bounds every field, and accepts an absent
or empty optional value while checking the email format when one is given.
CustomerConverter maps to and from the core dto and builds the PageResponse.

Payload classes stay distinct from core dto classes.
CustomerController follows the README table with declarative
@PreAuthorize("hasPermission('CUSTOMER', 'READ'|'WRITE')"), calls only
CustomerService and answers with an ApiResponse. The list validates page and
size, and a creation returns 201.

There is no business logic in the controller.
The customer ServiceConfig is added to the WebMvcConfig import list by fully
qualified name, because every core module has a class with that simple name.

Its DaoConfig comes along through the ServiceConfig import.
… module

The customer DAO tests run against a real PostgreSQL, so the module declares the
Testcontainers JUnit and PostgreSQL artifacts and the common test-jar holding
the shared singleton container, all with test scope, like the auth and catalog
modules.
The DAO test loads its own dedicated fixture, which truncates the tables it
touches and loads customers, one of them with NULL optional columns. It checks CRUD,
the NULL round trip, page ordering, offset and an out-of-range page, the count, an
update returning one affected row for a known id and zero for an unknown one, and a
delete blocked by the orders foreign key.

The order that blocks the delete is inserted by SQL in the test, so the
constraint is proven before the order module exists.
The RowMapper is fed a mocked ResultSet, including NULL telephone, email and
address, and the entity mapper is checked with hand-built objects and no mocks.
With a mocked DAO the tests cover the page bounds, the 404 cases, trimming, blank
optional fields becoming null, the length limits at their exact boundaries, every
email format case, an update that affects no row, and the foreign key race on delete
becoming a business refusal.
The real security configuration and a real signed JWT are used with a mocked
service. Every endpoint is checked for status and JSON shape, NULL optional fields in
the response, optional fields absent or empty, validation errors per field, page and
size violations, an out-of-range page keeping content as an empty array, and the 404
and 422 mappings.
A parameterized test asserts, for each of the five endpoints, that no token or a
revoked one gives 401, that a token holding every other permission or none gives
403, and that a token holding only the required CUSTOMER permission succeeds.
Real services and a real database prove the CRUD, NULL optional fields on create and
update, the two email failure modes (400 from Bean Validation, 422 from the service),
the 404 cases and the pagination bounds.

A customer with an order, inserted by SQL, cannot be deleted: the answer is 422
"Customer is referenced by orders" and the customer stays, then the delete succeeds
once the order is gone. Permission-less and write-only users get 403.
The three customer tasks are checked: the entity, dto, RowMapper, mapper, DAO,
service and configuration of core/customer, the controller and converter in ws
with the WebMvcConfig import, and the tests at all five layers with a dedicated
fixture for the DAO test class.
@EdgarEldy
EdgarEldy merged commit 2886b43 into develop Sep 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant